﻿using System.Linq;
using System.Collections.Generic;
using Godot.core;

namespace Godot.scene.main
{
    public partial class Node : Object
    {
        private __Data __data;

        protected void _block() 
        { 
            __data.blocked++; 
        }
        
        protected void _unblock() 
        { 
            __data.blocked--; 
        }

        protected new void _notification(int p_notification)
        {
            switch(p_notification) 
            {
		        case NOTIFICATION_PROCESS: {
			        if (script_instance != null) 
                    {
				        Variant time = get_process_delta_time();
				        Variant[] ptr = new Variant[1]{ time };
				        Variant.CallError err = new Variant.CallError();
				        script_instance.call_multilevel(SceneStringNames.singleton._process, ptr);
			        }
		        } break;
		        
                case NOTIFICATION_FIXED_PROCESS: {
			        if (script_instance != null) 
                    {
				        Variant time = get_fixed_process_delta_time();
				        Variant[] ptr = new Variant[1]{ time };
				        Variant.CallError err = new Variant.CallError();
				        script_instance.call_multilevel(SceneStringNames.singleton._fixed_process, ptr);
			        }

		        } break;    

		        case NOTIFICATION_ENTER_SCENE: {
			        if (__data.pause_mode == PauseMode.PAUSE_MODE_INHERIT) 
                    {
				        if (__data.parent != null)
					        __data.pause_owner = __data.parent.__data.pause_owner;
				        else
					        __data.pause_owner = null;
			        } 
                    else 
                    {
				        __data.pause_owner = this;
			        }
			        get_scene().__node_count++;
		        } break;

		        case NOTIFICATION_EXIT_SCENE: {
			        get_scene().__node_count--;
		        } break;

		        case NOTIFICATION_READY: {
			        if (script_instance != null) 
                    {
				        Variant.CallError err = new Variant.CallError();
				        script_instance.call_multilevel_reversed(SceneStringNames.singleton._ready, null);
			        }
			        //emit_signal(SceneStringNames::get_singleton()->enter_scene);face
		        } break;

		        case NOTIFICATION_POSTINITIALIZE: {
			        __data.in_constructor=false;
		        } break;

		        case NOTIFICATION_PREDELETE: {
			        set_owner(null);
			
			        while (__data.owned.Count != 0) 
                    {		
				        __data.owned.First().set_owner(null);	
			        }

			        if (__data.parent != null) 
                    {	
				        __data.parent.remove_child(this);
			        }

			        // kill children as cleanly as possible
			        while(__data.children.Count != 0) 
                    {	
				        Node child = __data.children[0];
				        remove_child(child);
				        //memdelete( child );
			        }

		        } break;
	        }
        }

        protected void _set_owner_nocheck(Node p_owner) 
        {
	        __data.owner = p_owner;
	        __data.owner.__data.owned.AddLast( this );
	        __data.OW = __data.owner.__data.owned.Last;
        }

        internal void _set_scene(SceneMainLoop p_scene) 
        {

	        SceneMainLoop tree_changed_a= null;
	        SceneMainLoop tree_changed_b = null;

        //	ERR_FAIL_COND(p_scene && data.parent && !data.parent->data.scene); //nobug if both are null

	        if (__data.scene != null) 
            {
		        _propagate_exit_scene();

		        tree_changed_a = __data.scene;
	        }


	        __data.scene = p_scene;

	        if (__data.scene != null) 
            {
		        _propagate_enter_scene();
		        _propagate_ready(); //reverse_notification(NOTIFICATION_READY);

		        tree_changed_b = __data.scene;

	        }

	        if (tree_changed_a != null)
		        tree_changed_a.tree_changed();
	        if (tree_changed_b != null)
		        tree_changed_b.tree_changed();

        }

        protected void _propagate_validate_owner() 
        {
	        if (__data.owner != null) 
            {
		        bool found = false;
		        Node parent = __data.parent;

		        while(parent != null) 
                {
			        if (parent == __data.owner) 
                    {
				        found = true;
				        break;
			        }

			        parent = parent.__data.parent;
		        }

		        if (!found) 
                {
			        __data.owner.__data.owned.Remove(__data.OW);
			        __data.owner = null;
		        }
	        }


	        for(int i = 0; i < __data.children.Count; i++) 
            {
		        __data.children[i]._propagate_validate_owner();
	        }
        }

        protected void _propagate_enter_scene() 
        {
	        // this needs to happen to all childs before any ENTER_SCENE

	        if (__data.parent != null) 
            {
		        __data.scene = __data.parent.__data.scene;
		        __data.depth = __data.parent.__data.depth + 1;
	        } 
            else 
            {
		        __data.depth = 1;
	        }
	
	        __data.inside_scene = true;

            foreach(StringName K in __data.grouped.Keys)
            {
		        __data.scene.add_to_group(K, this);
	        }

	        notification(NOTIFICATION_ENTER_SCENE);

	        if (script_instance != null) 
            {
		        Variant.CallError err = new Variant.CallError();
		        script_instance.call_multilevel_reversed(SceneStringNames.singleton._enter_scene, null);
	        }

	        emit_signal(SceneStringNames.singleton.enter_scene);

	        __data.blocked++;

	        //block while adding children
	        for (int i = 0; i < __data.children.Count; i++) 
            {	
		        if (!__data.children[i].is_inside_scene()) // could have been added in ENTER_SCENE
			        __data.children[i]._propagate_enter_scene();
	        }	

	        __data.blocked--;
	        // enter groups
        }

        protected void _propagate_exit_scene() 
        {
	        //block while removing children
	        __data.blocked++;

	        for (int i = __data.children.Count -1; i >= 0; i--) 
            {
		        __data.children[i]._propagate_exit_scene();
	        }

	        __data.blocked--;

	        if (script_instance != null) 
            {
		        Variant.CallError err = new Variant.CallError();
		        script_instance.call_multilevel(SceneStringNames.singleton._exit_scene, null);
	        }
	        emit_signal(SceneStringNames.singleton.exit_scene);

	        notification(NOTIFICATION_EXIT_SCENE,true);
	        if (__data.scene != null)
		        __data.scene.node_removed(this);

	        // exit groups;
            foreach(StringName K in __data.grouped.Keys)
            {
		        __data.scene.remove_from_group(K, this);
	        }

	        if (__data.scene != null)
		        __data.scene.tree_changed();

	        __data.inside_scene = false;
	        __data.scene = null;
	        __data.depth = -1;

        }

        protected void _propagate_ready() 
        {
	        __data.blocked++;
	        for (int i = 0; i < __data.children.Count; i++) 
            {
		        __data.children[i]._propagate_ready();
	        }
	        __data.blocked--;
	        notification(NOTIFICATION_READY);

        }

        protected void _validate_child_name(Node p_child)
        {
            throw new System.NotImplementedException();
        }

        protected static new string _get_category() 
        { 
            return "Nodes"; 
        }

        protected static new void _bind_methods()
        {

        }

        public SceneMainLoop get_scene()
        { 
            if( __data.scene == null) return null; 
            return __data.scene; 
        }

        public Node get_parent()
        {
            return __data.parent;
        }

        public void set_owner(Node p_owner) 
        {	
	        if (__data.owner != null) 
            {	
		        __data.owner.__data.owned.Remove( __data.OW );
		        __data.OW = null;
		        __data.owner = null;
	        }
	
	        if(p_owner == this)
                return;
	
	        if (p_owner == null)
		        return;
		
	        Node check = this.get_parent();
	        bool owner_valid = false;
	
	        while(check != null) 
            {		
		        if (check == p_owner) 
                {
			        owner_valid = true;
			        break;
		        }
		
		        check = check.__data.parent;
	        }
		
	        if(!owner_valid) return;

	        _set_owner_nocheck(p_owner);
        }

        public float get_process_delta_time()
        {
	        if (__data.scene != null)
		        return __data.scene.get_idle_process_time();
	        else
		        return 0;
        }   

        public float get_fixed_process_delta_time() 
        {	
	        if (__data.scene != null)
		        return __data.scene.get_fixed_process_time();
	        else
		        return 0;
        }

        public bool is_inside_scene() 
        { 
            return __data.inside_scene; 
        }

        public void remove_child(Node p_child) 
        {
	        if(p_child == null) return;

	        if(__data.blocked > 0 ) return;
	
	        int idx = -1;
	        for (int i = 0; i < __data.children.Count; i++) 
            {	
		        if (__data.children[i] == p_child) 
                {	
			        idx = i;
			        break;
		        }
	        }
	
	        if( idx == -1 ) return;

	
	        //if (data.scene) { does not matter
		
		        p_child._set_scene(null);
	        //}
	
	        remove_child_notify(p_child); 
	        p_child.notification(NOTIFICATION_UNPARENTED);
		
	        __data.children.RemoveAt(idx);
	
	        for (int i = idx; i < __data.children.Count; i++) 
            {	
		        __data.children[i].__data.pos = i;
	        }

            p_child.__data.parent = null;
	        p_child.__data.pos = -1;



	        // validate owner
	        p_child._propagate_validate_owner();
		
        }

        public Node get_child(int p_index)
        {	
	        if(p_index >= __data.children.Count)return null;	
	        return __data.children[p_index];
        }

        public int get_child_count()
        {	
	        return __data.children.Count;	
        }

        public void set_name(string p_name) 
        {	
	        string name = p_name.Replace(":","").Replace("/","").Replace("@","");

	        if(name == "") return;
	        
            __data.name = name;
	
	        if (__data.parent != null) 
            {	
		        __data.parent._validate_child_name(this);
	        }

	        if (is_inside_scene()) 
            {
		        emit_signal("renamed");
		        get_scene().tree_changed();
	        }
        }

        public virtual void remove_child_notify(Node p_child)
        {
            // to be used when not wanted
        }

        public virtual void add_child_notify(Node p_child)
        {
            // to be used when not wanted
        }

        public Node()
        {
            __data = new __Data();
            __data.pos = -1;
            __data.depth = -1;
            __data.blocked = 0;
            __data.parent = null;
            __data.scene = null;
            __data.fixed_process = false;
            __data.idle_process = false;
            __data.inside_scene = false;

            __data.owner = null;
            __data.OW = null;
            __data.input = false;
            __data.unhandled_input = false;
            __data.pause_mode = PauseMode.PAUSE_MODE_INHERIT;
            __data.pause_owner = null;
            __data.parent_owned = false;
            __data.in_constructor = true;
        }
    }

    public partial class Node
    {
        public enum PauseMode
        {
            PAUSE_MODE_INHERIT,
            PAUSE_MODE_STOP,
            PAUSE_MODE_PROCESS
        }

        #region Notification 
		    // you can make your own, but don't use the same numbers as other notifications in other nodes
		    public const int NOTIFICATION_ENTER_SCENE = 10;
		    public const int NOTIFICATION_EXIT_SCENE = 11;
		    public const int NOTIFICATION_MOVED_IN_PARENT = 12;
		    public const int NOTIFICATION_READY = 13;
		    //NOTIFICATION_PARENT_DECONFIGURED =15, - it's confusing, it's going away
		    public const int NOTIFICATION_PAUSED = 14;
		    public const int NOTIFICATION_UNPAUSED = 15;
		    public const int NOTIFICATION_FIXED_PROCESS = 16;
		    public const int NOTIFICATION_PROCESS = 17;
		    public const int NOTIFICATION_PARENTED = 18;
            public const int NOTIFICATION_UNPARENTED = 19;
        #endregion

        public class GroupInfo
        {
            public string name;
            public bool persistent;
        };

        private class __GroupData
        {
            public bool persistent;
        }

        private class __Data
        {
            public string filename;
		    public Dictionary<Variant, Variant> instance_state;
		    public List<StringName> instance_groups;
		    public List<Connection> instance_connections;

		    public Node parent;
		    public Node owner;
		    public List<Node> children;	// list of children
		    public int pos;
		    public int depth;
		    public int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
		    public StringName name;
		    public SceneMainLoop scene;
		    public bool inside_scene;

				
		    public Dictionary<StringName, __GroupData>  grouped;
		    public LinkedListNode<Node> OW; // owned element
		    public LinkedList<Node> owned;
		
		    public PauseMode pause_mode;
		    public Node pause_owner;
		    
            // variables used to properly sort the node when processing, ignored otherwise
		    public bool fixed_process;
		    public bool idle_process;

		    public bool input;
		    public bool unhandled_input;

		    public bool parent_owned;
		    public bool in_constructor;

            public __Data()
            {
                instance_state = new Dictionary<Variant, Variant>();
                instance_groups = new List<StringName>();
                instance_connections = new List<Connection>();
                children = new List<Node>();
                grouped = new Dictionary<StringName, __GroupData>();
                owned = new LinkedList<Node>();
            }
        }
    }

    public partial class Node
    {
        #region OBJ_TYPE( Node, Object )
        /* TYPE API */
        public override string get_type() { return "Node"; }

        public override string get_save_type() { return get_type(); } //type stored when saving

        public override StringName get_type_name() { return (StringName)get_type_static(); }

        public override bool is_type(string p_type) { return (p_type == "Node") ? true : base.is_type(p_type); }

        protected override void _initialize_typev() { initialize_type(); }

        protected override bool _setv(StringName p_name, Variant p_property)
        {
            if (base._setv(p_name, p_property))
                return true;

            if (typeof(Node).GetMethod("_set") != typeof(Object).GetMethod("_set"))
            {
                return _set(p_name, p_property);
            }
            return false;
        }

        protected override bool _getv(StringName p_name, ref Variant r_property)
        {
            if (typeof(Node).GetMethod("_get") != typeof(Object).GetMethod("_get"))
            {
                if (_get(p_name, ref r_property))
                    return true;
            }

            return base._getv(p_name, ref r_property);
        }

        protected override void _get_property_listv(ref List<PropertyInfo> p_list, bool p_reversed)
        {
            if (!p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }

            p_list.Add(new PropertyInfo(Variant.Type.NIL, get_type_static(), PropertyHint.PROPERTY_HINT_NONE, "", (int)PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));

            if (!_is_gpl_reversed())
                ObjectTypeDB.get_property_list("Node", ref p_list, true);

            if (typeof(Node).GetMethod("_get_property_list") != typeof(Object).GetMethod("_get_property_list"))
            {
                _get_property_list(ref p_list);
            }

            if (_is_gpl_reversed())
                ObjectTypeDB.get_property_list("Node", ref p_list, true);

            if (p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }
        }

        protected override void _notificationv(int p_notification, bool p_reversed)
        {
            if (!p_reversed)
                base._notificationv(p_notification, p_reversed);

            if (typeof(Node).GetMethod("_notification") != typeof(Object).GetMethod("_notification"))
            {
                _notification(p_notification);
            }

            if (p_reversed)
                base._notificationv(p_notification, p_reversed);
        }

        public static new string get_type_static() { return "Node"; }

        public static new string get_parent_type_static() { return Object.get_type_static(); }

        public static new void get_inheritance_list_static(ref List<string> r_inheritance_list)
        {
            Object.get_inheritance_list_static(ref r_inheritance_list);
            r_inheritance_list.Add(get_type_static());
        }

        public static new string get_category_static()
        {
            string category = Object.get_category_static();
            if (typeof(Node).GetMethod("_get_category") != typeof(Object).GetMethod("_get_category"))
            {
                if (category != "")
                    category += "/";
                category += _get_category();
            }
            return category;
        }

        public static new string inherits_static() { return "Object"; }

        public static new void get_valid_parents_static(ref List<string> p_parents)
        {
            if (typeof(Node).GetMethod("_get_valid_parents_static") != typeof(Object).GetMethod("_get_valid_parents_static"))
            {
                Node._get_valid_parents_static(ref p_parents);
            }

            Object.get_valid_parents_static(ref p_parents);
        }

        public static new void initialize_type()
        {
            if (__initialized)
                return;
            Object.initialize_type();
            ObjectTypeDB._add_type<Node>();
            if(typeof(Node).GetMethod("_bind_methods") != typeof(Object).GetMethod("_bind_methods"))
                _bind_methods();
            __initialized = true;
        }

        private static bool __initialized = false;
        #endregion
    }
}
